//import com.sun.xml.internal.ws.api.pipe.NextAction;
import java.awt.Color;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import org.xml.sax.*;
import org.w3c.dom.*;

/**
 * A class used for its static methods.
 * It handles the writing of a graph and a partition in GraphML format
 */
abstract public class GraphMLWriter {
    
    /**
     * Outputs a graph in GraphML format into the filename given as the parameter xml_filename
     * @param graph the graph output
     * @param xml_filename the filename to output the graph to
     */
    public static void graphToGraphML(Graph graph, String xml_filename) {
        Document dom;
        Element e = null, graphElement = null, dataElement = null;
        int num_vertices = graph.getVertexCount();
        Edge[] edges = graph.getEdgeArray();

        // instance of a DocumentBuilderFactory
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        try {
            // use factory to get an instance of document builder
            DocumentBuilder db = dbf.newDocumentBuilder();
            // create instance of DOM
            dom = db.newDocument();

            // create the root element
            Element rootEle = dom.createElement("graphml");
            
            //create the graph node
            graphElement = dom.createElement("graph");
            graphElement.setAttribute("id","G");
            graphElement.setAttribute("edgedefault", "undirected"); 
            rootEle.appendChild(graphElement);
            
            //Create node elements
            for (int i=0;i<num_vertices;i++)
            {
                e = dom.createElement("node");
                e.setAttribute("id", String.valueOf(i + 1) + ".0");
                
                graphElement.appendChild(e);
            }
            
            //Create edge elements
            for (Edge curr_edge : edges)
            {
                e = dom.createElement("edge");
                //Doesn't matter what is the source and what is the target, as we det teh edgedefault attribute to be "undirected"
                e.setAttribute("source", String.valueOf(curr_edge.firstEnd + 1) + ".0");
                e.setAttribute("target", String.valueOf(curr_edge.secondEnd + 1) + ".0");
                graphElement.appendChild(e);
            }
            
            dom.appendChild(rootEle);

            //TODO understand what these output properties are, maybe change it up
            try {
                Transformer tr = TransformerFactory.newInstance().newTransformer();
                tr.setOutputProperty(OutputKeys.INDENT, "yes");
                tr.setOutputProperty(OutputKeys.METHOD, "xml");
                tr.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
                tr.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "roles.dtd");
                tr.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");

                // send DOM to file
                tr.transform(new DOMSource(dom), 
                                     new StreamResult(new FileOutputStream(xml_filename)));

            } catch (TransformerException te) {
                System.out.println(te.getMessage());
            } catch (IOException ioe) {
                System.out.println(ioe.getMessage());
            }
        } catch (ParserConfigurationException pce) {
            System.out.println("UsersXML: Error trying to instantiate DocumentBuilder " + pce);
        }
    }
    
    /**
     * Represents a color for a community using RGB values
     */
    public static class CommunityColor
    {
        public int r;
        public int g;
        public int b;
        
        public CommunityColor(int param_r, int param_g, int param_b)
        {
            r = param_r; 
            g = param_g; 
            b = param_b;
        }
    }
    
    /**
     * A set of pre-defined community colors used when outputting a partition to a GraphML file
     */
    public static final CommunityColor[] communityColors =
    {
        new CommunityColor(255,0,0), //red
        new CommunityColor(0,255,0), //blue
        new CommunityColor(0,0,255), //green
        new CommunityColor(255,255,0), //yellow
        new CommunityColor(128,0,128), //purple
        new CommunityColor(255,165,0), //orange
        new CommunityColor(197,193,170), //light grey
        new CommunityColor(255,255,255), //white
        new CommunityColor(0,0,0), //black
        new CommunityColor(0,245,255) //turquoise
    };
    
    /**
     * Outputs a community-structure in GraphML format into the filename given as the parameter xml_filename
     * @param graph the underlying graph partitioned
     * @param communiy_collection the collection of communities
     * @param xml_filename the filename to output the partition to
     */
    static public void partitionToGraphML(Graph graph, CommunityCollection community_collection, String xml_filename) {
        Document dom;
        Element e = null, graphElement = null, dataElement = null;
        Edge[] edges = graph.getEdgeArray();
        Community[] communities = community_collection.toArray();
        CommunityNode curr_node;
        int community_index=0;
        CommunityColor curr_color;
                
        // instance of a DocumentBuilderFactory
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        try {
            // use factory to get an instance of document builder
            DocumentBuilder db = dbf.newDocumentBuilder();
            // create instance of DOM
            dom = db.newDocument();

            // create the root element
            Element rootEle = dom.createElement("graphml");

            //Create the color attributes: r, g and b
            //The r attribute (for red)
            e = dom.createElement("key");
            e.setAttribute("attr.name", "r");
            e.setAttribute("id", "r");
            e.setAttribute("attr.type", "int");
            e.setAttribute("for", "node");
            rootEle.appendChild(e);
            
            //The g attribute (for green)
            e = dom.createElement("key");
            e.setAttribute("attr.name", "g");
            e.setAttribute("id", "g");
            e.setAttribute("attr.type", "int");
            e.setAttribute("for", "node");
            rootEle.appendChild(e);
            
            //The b attribute (for blue)
            e = dom.createElement("key");
            e.setAttribute("attr.name", "b");
            e.setAttribute("id", "b");
            e.setAttribute("attr.type", "int");
            e.setAttribute("for", "node");
            rootEle.appendChild(e);
            
            //create the graph node
            graphElement = dom.createElement("graph");
            graphElement.setAttribute("id","G");
            graphElement.setAttribute("edgedefault", "undirected"); 
            rootEle.appendChild(graphElement);
            
            for (Community c : communities)
            {
                curr_node = c.getHead();
                
                while (curr_node != null)
                {
                    //The node represents a vertex in the graph
                    
                    e = dom.createElement("node");
                    //Be careful here, do we need to add the 1 or not? depends on the original file really. Until now we assumed it starts with 1 so to keep it consistent i add the 1 back here
                    e.setAttribute("id", /*"n" + */String.valueOf(curr_node.value + 1) + ".0" );

                    if (community_index >= communityColors.length)
//                        throw new ArrayIndexOutOfBoundsException("Not enough colors defined in GraphMLWriter, cannot assign colour to community with index " + community_index);
                        curr_color = communityColors[communityColors.length - 1];
                    else
                        curr_color = communityColors[community_index];
                    
                    //Add a data element for the r attribute
                    dataElement = dom.createElement("data");
                    dataElement.setAttribute("key","r");
                    dataElement.appendChild(dom.createTextNode(String.valueOf(curr_color.r)));
                    e.appendChild(dataElement);

                    //Add a data element for the g attribute
                    dataElement = dom.createElement("data");
                    dataElement.setAttribute("key","g");
                    dataElement.appendChild(dom.createTextNode(String.valueOf(curr_color.g)));
                    e.appendChild(dataElement);

                    //Add a data element for the b attribute
                    dataElement = dom.createElement("data");
                    dataElement.setAttribute("key","b");
                    dataElement.appendChild(dom.createTextNode(String.valueOf(curr_color.b)));
                    e.appendChild(dataElement);

                    graphElement.appendChild(e);
                    curr_node = curr_node.next;
                }
                
                community_index++;
            }
            
            //Create edge elements
            for (Edge curr_edge : edges)
            {
                e = dom.createElement("edge");
                //Doesn't matter what is the source and what is the target, as we det teh edgedefault attribute to be "undirected"
                e.setAttribute("source", String.valueOf(curr_edge.firstEnd + 1) + ".0");
                e.setAttribute("target", String.valueOf(curr_edge.secondEnd + 1) + ".0");
                graphElement.appendChild(e);
            }

            dom.appendChild(rootEle);

            try {
                Transformer tr = TransformerFactory.newInstance().newTransformer();
                tr.setOutputProperty(OutputKeys.INDENT, "yes");
                tr.setOutputProperty(OutputKeys.METHOD, "xml");
                tr.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
                tr.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "roles.dtd");
                tr.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");

                // send DOM to file
                tr.transform(new DOMSource(dom), 
                                     new StreamResult(new FileOutputStream(xml_filename)));

            } catch (TransformerException te) {
                System.out.println(te.getMessage());
            } catch (IOException ioe) {
                System.out.println(ioe.getMessage());
            }
        } catch (ParserConfigurationException pce) {
            System.out.println("UsersXML: Error trying to instantiate DocumentBuilder " + pce);
        }
    }
}
